/*
 * 描          述:  <描述>
 * 修  改   人:  Administrator
 * 修改时间:  2020年2月28日
 * <修改描述:>
 */
package com.tx.component.file.resource.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;

import com.tx.component.file.exception.FileContextResourceException;
import com.tx.component.file.resource.AbstractFCResource;
import com.tx.component.file.resource.FCResource;
import com.tx.component.file.resource.FCResourceFilter;
import com.tx.component.file.resource.FCResourceLoader;
import com.tx.component.file.resource.FCResourceTypeEnum;
import com.tx.component.file.util.FileContextUtils;
import com.tx.component.file.util.MinIOUtils;
import com.tx.core.exceptions.util.AssertUtils;
import com.tx.core.util.MessageUtils;

import io.minio.BucketExistsArgs;
import io.minio.GetBucketVersioningArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.StatObjectArgs;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import io.minio.messages.VersioningConfiguration;

/**
 * MinIO文件资源加载器<br/>
 * http://docs.minio.org.cn/docs/
 * 
 * @author  Administrator
 * @version  [版本号, 2020年2月28日]
 * @see  [相关类/方法]
 * @since  [产品/模块版本]
 */
public class MinIOFCResourceLoader
        implements FCResourceLoader, InitializingBean {
    
    /** 日志记录句柄 */
    protected static Logger logger = LoggerFactory
            .getLogger(FCResourceLoader.class);
    
    /** 文件夹目录 */
    private String catalog;
    
    /** ossClient */
    private MinioClient minioClient;
    
    /** bucketName */
    private String bucketName;
    
    /** <默认构造函数> */
    public MinIOFCResourceLoader() {
        super();
    }
    
    /** <默认构造函数> */
    public MinIOFCResourceLoader(MinioClient minioClient, String bucketName) {
        super();
        this.minioClient = minioClient;
        this.bucketName = bucketName;
    }
    
    /**
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() {
        AssertUtils.notEmpty(this.minioClient, "minioClient is null.");
        AssertUtils.notEmpty(this.bucketName, "bucketName is empty.");
        
        try {
            if (!this.minioClient.bucketExists(BucketExistsArgs.builder()
                    .bucket(this.bucketName)
                    .build())) {
                this.minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(this.bucketName)
                        .region("cn-chongqing-1")
                        .build());
            }
            
            //如果存在则读取bucket信息，如果已被占用，则此处会抛出异常
            VersioningConfiguration version = this.minioClient
                    .getBucketVersioning(GetBucketVersioningArgs.builder()
                            .bucket(this.bucketName)
                            .build());
            logger.info(
                    "bucket init success. type: minio,bucketName: {}, version:{}",
                    this.bucketName,
                    version.toString());
        } catch (InvalidKeyException | ErrorResponseException
                | InsufficientDataException | InternalException
                | InvalidResponseException | NoSuchAlgorithmException
                | ServerException | XmlParserException
                | IllegalArgumentException | IOException e) {
            throw new FileContextResourceException(
                    "init minio resourceLoader error. ", e);
        }
    }
    
    /**
     * @param fd
     * @return
     */
    @Override
    public FCResource getFCResource(String relativePath) {
        relativePath = relativePath == null ? "" : relativePath;
        //预处理关联路径
        relativePath = FileContextUtils.handleRelativePath(relativePath);
        
        //创建关联资源
        FCResource fileResource = new MinIOFCResource(this.catalog,
                this.minioClient, this.bucketName, relativePath);
        return fileResource;
    }
    
    /**
     * @param relativePath
     * @param type
     * @return
     */
    @Override
    public FCResource getFCResource(String relativePath,
            FCResourceTypeEnum type) {
        AssertUtils.notNull(type, "type is null.");
        
        relativePath = relativePath == null ? "" : relativePath;
        //预处理关联路径
        relativePath = FileContextUtils.handleRelativePath(relativePath);
        
        //创建关联资源
        FCResource fileResource = new MinIOFCResource(this.catalog,
                this.minioClient, this.bucketName, relativePath, type);
        return fileResource;
    }
    
    /**
     * OSS文件资源<br/>
     * <功能详细描述>
     * 
     * @author  Administrator
     * @version  [版本号, 2020年3月11日]
     * @see  [相关类/方法]
     * @since  [产品/模块版本]
     */
    public static class MinIOFCResource extends AbstractFCResource {
        
        /** minioClient */
        private MinioClient minioClient;
        
        /** bucketName */
        private String bucketName;
        
        /** 文件定义 */
        private String relativePath;
        
        /** <默认构造函数> */
        public MinIOFCResource(String catalog, MinioClient minioClient,
                String bucketName, String relativePath) {
            super(catalog);
            AssertUtils.notNull(minioClient, "minioClient is null.");
            AssertUtils.notEmpty(bucketName, "bucketName is empty.");
            AssertUtils.notEmpty(relativePath, "relativePath is empty.");
            
            //处理相对路径
            relativePath = FileContextUtils.handleRelativePath(relativePath);
            
            this.relativePath = relativePath;
            this.bucketName = bucketName;
            this.minioClient = minioClient;
            
            setType(FCResourceTypeEnum.FILE);
        }
        
        /** <默认构造函数> */
        public MinIOFCResource(String catalog, MinioClient minioClient,
                String bucketName, String relativePath,
                FCResourceTypeEnum type) {
            super(catalog);
            AssertUtils.notNull(minioClient, "minioClient is null.");
            AssertUtils.notEmpty(bucketName, "bucketName is empty.");
            AssertUtils.notEmpty(relativePath, "relativePath is empty.");
            
            //处理相对路径
            relativePath = FileContextUtils.handleRelativePath(relativePath);
            
            this.relativePath = relativePath;
            this.bucketName = bucketName;
            this.minioClient = minioClient;
            
            setType(type);
        }
        
        /**
         * @return
         * @throws FileContextResourceException
         */
        @Override
        public FCResourceTypeEnum getType()
                throws FileContextResourceException {
            return this.type;
        }
        
        /**
         * @return 返回 relativePath
         */
        @Override
        public String getRelativePath() {
            return relativePath;
        }
        
        /**
         * @param relativePath
         * @return
         * @throws IOException
         */
        @Override
        public FCResource createRelative(String relativePath) {
            //拼接相对路径
            String pathToUse = StringUtils.applyRelativePath(getRelativePath(),
                    relativePath);
            
            //组装实际资源对象
            FCResource resource = new MinIOFCResource(this.getCatalog(),
                    this.minioClient, this.bucketName, pathToUse);
            return resource;
        }
        
        /**
         * @param name
         * @return
         */
        @Override
        public FCResource getChild(String name) {
            //拼接相对路径
            String pathToUse = StringUtils
                    .applyRelativePath(getRelativePath() + "/", name);
            
            //组装实际资源对象
            FCResource resource = new MinIOFCResource(this.getCatalog(),
                    this.minioClient, this.bucketName, pathToUse);
            return resource;
        }
        
        /**
         * @return
         */
        @Override
        public FCResource getParent() {
            //拼接相对路径
            String pathToUse = StringUtils.applyRelativePath(getRelativePath(),
                    "");
            
            //组装实际资源对象
            FCResource resource = new MinIOFCResource(this.getCatalog(),
                    this.minioClient, this.bucketName, pathToUse,
                    FCResourceTypeEnum.FOLDER);
            return resource;
        }
        
        /**
         * 文件是否存在<br/>
         * @return
         */
        @Override
        public boolean exists() throws FileContextResourceException {
            try {
                boolean flag = MinIOUtils.exists(this.minioClient,
                        this.bucketName,
                        this.relativePath);
                if (flag) {
                    return true;
                } else {
                    if (FCResourceTypeEnum.FOLDER.equals(getType())) {
                        return true;
                    } else {
                        return false;
                    }
                }
            } catch (InvalidKeyException | ErrorResponseException
                    | IllegalArgumentException | InsufficientDataException
                    | InternalException | InvalidResponseException
                    | NoSuchAlgorithmException | ServerException
                    | XmlParserException | IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.exists() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * 获取文件资源大小<br/>
         * @return
         * @throws FileContextResourceException
         */
        @Override
        public long getSize() throws FileContextResourceException {
            long size = -1;
            try {
                size = this.minioClient.statObject(StatObjectArgs.builder()
                        .bucket(this.bucketName)
                        .object(this.relativePath)
                        .build()).size();
            } catch (InvalidKeyException | ErrorResponseException
                    | InsufficientDataException | InternalException
                    | InvalidResponseException | NoSuchAlgorithmException
                    | ServerException | XmlParserException
                    | IllegalArgumentException | IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getSize() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
            return size;
        }
        
        /**
         * 
         */
        @Override
        public void delete() throws FileContextResourceException {
            if (this.exists()) {
                try {
                    this.minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(this.bucketName)
                            .object(this.relativePath)
                            .build());
                } catch (InvalidKeyException | ErrorResponseException
                        | InsufficientDataException | InternalException
                        | InvalidResponseException | NoSuchAlgorithmException
                        | ServerException | XmlParserException
                        | IllegalArgumentException | IOException e) {
                    throw new FileContextResourceException(MessageUtils.format(
                            "FCResource.delete() error. catalog:{},path:{}.",
                            getCatalog(),
                            getRelativePath()), e);
                }
            }
        }
        
        /**
         * 
         */
        @Override
        public void deleteQuietly() throws FileContextResourceException {
            if (isFile()) {
                delete();
            } else if (isFolder()) {
                try {
                    List<Item> items = new ArrayList<>();
                    Iterable<Result<Item>> iterable = this.minioClient
                            .listObjects(ListObjectsArgs.builder()
                                    .bucket(this.bucketName)
                                    .prefix(this.relativePath)
                                    .recursive(true)
                                    .build());
                    for (Result<Item> t : iterable) {
                        Item i = t.get();
                        if (!i.isDir()) {
                            items.add(i);
                        }
                    }
                    Iterable<Result<DeleteError>> results = this.minioClient
                            .removeObjects(RemoveObjectsArgs.builder()
                                    .bucket(this.bucketName)
                                    .objects(items.stream()
                                            .map(t -> new DeleteObject(
                                                    t.objectName()))
                                            .collect(Collectors.toList()))
                                    .build());
                    //此处一定需要遍历结果，否则不会进行实际删除
                    if (!IterableUtils.isEmpty(results)) {
                        for (Result<DeleteError> result : results) {
                            DeleteError error = result.get();
                            logger.error("Error in deleting object "
                                    + error.objectName() + "; "
                                    + error.message());
                        }
                    }
                    
                } catch (InvalidKeyException | IllegalArgumentException
                        | ErrorResponseException | InsufficientDataException
                        | InternalException | InvalidResponseException
                        | NoSuchAlgorithmException | ServerException
                        | XmlParserException | IOException e) {
                    throw new FileContextResourceException(MessageUtils.format(
                            "FCResource.deleteQuietly() error. catalog:{},path:{}.",
                            getCatalog(),
                            getRelativePath()), e);
                }
            }
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public InputStream getInputStream()
                throws FileContextResourceException {
            try {
                GetObjectResponse ossObjectContent = this.minioClient
                        .getObject(GetObjectArgs.builder()
                                .bucket(this.bucketName)
                                .object(this.relativePath)
                                .build());
                return ossObjectContent;
            } catch (InvalidKeyException | ErrorResponseException
                    | InsufficientDataException | InternalException
                    | InvalidResponseException | NoSuchAlgorithmException
                    | ServerException | XmlParserException
                    | IllegalArgumentException | IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getInputStream() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * @param inputStream
         */
        @Override
        public void save(InputStream inputStream)
                throws FileContextResourceException {
            AssertUtils.notNull(inputStream, "inputStream is null.");
            
            //其返回结果中Response为空,所以只能依赖oss中抛出的异常来判断
            try {
                this.minioClient.putObject(PutObjectArgs.builder()
                        .bucket(this.bucketName)
                        .object(this.relativePath)
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
            } catch (InvalidKeyException | ErrorResponseException
                    | InsufficientDataException | InternalException
                    | InvalidResponseException | NoSuchAlgorithmException
                    | ServerException | XmlParserException
                    | IllegalArgumentException | IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.save(InputStream) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * @param inputStream
         */
        @Override
        public void add(InputStream inputStream)
                throws FileContextResourceException {
            AssertUtils.notNull(inputStream, "inputStream is null.");
            
            if (exists()) {
                throw new FileContextResourceException(
                        "新增资源错误，对应资源已经存在.如需覆盖请调用save方法.");
            }
            //其返回结果中Response为空,所以只能依赖oss中抛出的异常来判断
            try {
                // 开始上传
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(relativePath)
                        .contentType(getContentType())
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
            } catch (InvalidKeyException | ErrorResponseException
                    | InsufficientDataException | InternalException
                    | InvalidResponseException | NoSuchAlgorithmException
                    | ServerException | XmlParserException
                    | IllegalArgumentException | IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.add(InputStream) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * @param fileFilter
         * @param dirFilter
         * @param depthwise
         * @return
         * @throws FileContextResourceException
         */
        @Override
        public List<FCResource> findFiles(FCResourceFilter fileFilter,
                boolean depthwise) throws FileContextResourceException {
            Iterable<Result<Item>> results = this.minioClient
                    .listObjects(ListObjectsArgs.builder()
                            .bucket(this.bucketName)
                            .prefix(this.relativePath)
                            .recursive(depthwise)
                            .build());
            List<FCResource> resList = new ArrayList<>();
            try {
                for (Result<Item> result : results) {
                    Item item = result.get();
                    if (item.isDir()) {
                        FCResource res = new MinIOFCResource(getCatalog(),
                                this.minioClient, this.bucketName,
                                item.objectName(), FCResourceTypeEnum.FOLDER);
                        resList.add(res);
                    } else {
                        FCResource res = new MinIOFCResource(getCatalog(),
                                this.minioClient, this.bucketName,
                                item.objectName());
                        if (fileFilter != null) {
                            if (fileFilter.accept(res)) {
                                resList.add(res);
                            }
                        } else {
                            resList.add(res);
                        }
                    }
                }
            } catch (InvalidKeyException | ErrorResponseException
                    | IllegalArgumentException | InsufficientDataException
                    | InternalException | InvalidResponseException
                    | NoSuchAlgorithmException | ServerException
                    | XmlParserException | IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.findFiles(FCResourceFilter, boolean) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
            return resList;
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public List<FCResource> getChildren()
                throws FileContextResourceException {
            List<FCResource> resList = findFiles(null, false);
            return resList;
        }
        
        /**
         * 
         */
        @Override
        public void createFile() throws FileContextResourceException {
            //donothing
            this.type = FCResourceTypeEnum.FILE;
            //其返回结果中Response为空,所以只能依赖oss中抛出的异常来判断
            try (InputStream inputStream = IOUtils.toInputStream("", "UTF-8")) {
                this.minioClient.putObject(PutObjectArgs.builder()
                        .bucket(this.bucketName)
                        .object(this.relativePath)
                        .stream(inputStream, inputStream.available(), -1)
                        .contentType(getContentType())
                        .build());
            } catch (IOException | InvalidKeyException | ErrorResponseException
                    | InsufficientDataException | InternalException
                    | InvalidResponseException | NoSuchAlgorithmException
                    | ServerException | XmlParserException
                    | IllegalArgumentException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.save(InputStream) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * @throws IOException
         */
        @Override
        public void createFolder() throws FileContextResourceException {
            this.type = FCResourceTypeEnum.FOLDER;
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream getOutputStream()
                throws FileContextResourceException {
            throw new FileContextResourceException(
                    "unsupport method getOutputStream");
        }
        
        /**
         * @param bAppend
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream getOutputStream(boolean bAppend)
                throws FileContextResourceException {
            throw new FileContextResourceException(
                    "unsupport method getOutputStream");
        }
    }
    
}
